package com.siyoumi.app.external_api.aliyun;

import com.aliyun.oss.ClientBuilderConfiguration;
import com.aliyun.oss.OSS;
import com.aliyun.oss.OSSClientBuilder;
import com.aliyun.oss.OSSException;
import com.aliyun.oss.common.utils.BinaryUtil;
import com.aliyun.oss.model.*;
import com.aliyun.sts20150401.models.AssumeRoleResponse;
import com.aliyun.sts20150401.models.AssumeRoleResponseBody;
import com.aliyun.tea.TeaException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.siyoumi.app.external_api.aliyun.entity.EnvAlipayOss;
import com.siyoumi.component.LogPipeLine;
import com.siyoumi.component.XApp;
import com.siyoumi.component.XRedis;
import com.siyoumi.component.http.XHttpContext;
import com.siyoumi.config.SysConfig;
import com.siyoumi.exception.BaseExceptionHandler;
import com.siyoumi.exception.EnumSys;
import com.siyoumi.util.XDate;
import com.siyoumi.util.XJson;
import com.siyoumi.util.XReturn;
import com.siyoumi.util.XStr;
import com.siyoumi.validator.XValidator;
import lombok.Data;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.io.File;
import java.net.URL;
import java.time.ZoneOffset;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;

//阿里云oss
@Data
@Slf4j
public class ApiAliYunOss {
    EnvAlipayOss config;

    /**
     * 实例化
     *
     * @param config
     */
    static public ApiAliYun of(EnvAlipayOss config) {
        ApiAliYun api = new ApiAliYun();
        api.setConfig(config);
        return api;
    }

    protected String getBucketName() {
        return getConfig().getBucket_name();
    }


    //https://help.aliyun.com/zh/oss/developer-reference/simple-upload-11?spm=a2c4g.11186623.0.0.11e25b0fDf1Hcj
    public XReturn uploadFileByRoot(String root, String filePath) {
        File file = new File(filePath);
        if (!file.exists()) {
            return XReturn.getR(20052, "文件不存在");
        }

        //app/2025-01-05/3.jpg
        String ossFilePath = XStr.format("{0}/{1}/{2}"
                , root
                , XDate.toDateString() //日期目录
                , file.getName()
        );

        return uploadFile(ossFilePath, file);
    }

    /**
     * 上传文件
     *
     * @param ossFilePath sso保存路径
     * @param file        文件
     * @return
     */
    public XReturn uploadFile(String ossFilePath, File file) {
        //String[] fileArr = filePath.split("/");
        //String fileName = fileArr[fileArr.length - 1];
        //String fileName = file.getName();
        //LogPipeLine.getTl().setLogMsg("fileName: ", fileName);
        LogPipeLine.of().setLogMsg("ossFilePath: ", ossFilePath);

        OSS ossClient = getClient();
        XReturn r;
        try {
            // 创建PutObjectRequest对象。
            PutObjectRequest putObjectRequest = new PutObjectRequest(getBucketName(), ossFilePath, file);
            // 创建PutObject请求。
            PutObjectResult result = ossClient.putObject(putObjectRequest);

            r = XReturn.getR(0);
            r.setData("request_id", result.getRequestId());
            r.setData("oss_file_path", ossFilePath);
        } catch (OSSException oe) {
            log.error(oe.getMessage());

            r = EnumSys.SYS.getR(oe.getErrorMessage());
            r.setData("error_code", oe.getErrorCode());
            r.setData("request_id", oe.getRequestId());
            r.setData("host_id", oe.getHostId());
        } catch (Exception ex) {
            BaseExceptionHandler.printStackTrace(ex);

            log.error(ex.getMessage());
            r = EnumSys.SYS.getR();
            r.setErrMsg(ex.getMessage());
        } finally {
            if (ossClient != null) {
                ossClient.shutdown();
            }
        }

        errHandle("uploadFile", r);

        return r;
    }

    /**
     * 文件列表
     *
     * @param root 搜索前缀，如：export
     */
    public List<OSSObjectSummary> fileList(String root) {
        OSS ossClient = getClient();
        XReturn r;
        List<OSSObjectSummary> files = new ArrayList<>();
        try {
            ListObjectsRequest request = new ListObjectsRequest(getBucketName());
            request.setPrefix(root);
            ObjectListing result = ossClient.listObjects(request);

            r = XReturn.getR(0);
            files = result.getObjectSummaries();
        } catch (OSSException oe) {
            log.error(oe.getMessage());

            r = EnumSys.SYS.getR();
            r.setErrMsg(oe.getErrorMessage());
            r.setData("error_code", oe.getErrorCode());
            r.setData("request_id", oe.getRequestId());
            r.setData("host_id", oe.getHostId());
        } catch (Exception ex) {
            BaseExceptionHandler.printStackTrace(ex);

            log.error(ex.getMessage());
            r = EnumSys.SYS.getR();
            r.setErrMsg(ex.getMessage());
        } finally {
            if (ossClient != null) {
                ossClient.shutdown();
            }
        }

        errHandle("fileList", r);
        if (r.err()) {
            XValidator.err(r);
        }

        return files;
    }

    /**
     * 删除列表
     */
    public XReturn delFileList(List<String> keys) {
        OSS ossClient = getClient();
        XReturn r;
        try {
            DeleteObjectsRequest deleteObjectsRequest = new DeleteObjectsRequest(getBucketName()).withKeys(keys).withEncodingType("url");
            DeleteObjectsResult deleteObjectsResult = ossClient.deleteObjects(deleteObjectsRequest);
            List<String> deletedObjects = deleteObjectsResult.getDeletedObjects();

            r = XReturn.getR(0);
            r.setData("delete_result", deletedObjects);

        } catch (OSSException oe) {
            log.error(oe.getMessage());

            r = EnumSys.SYS.getR();
            r.setErrMsg(oe.getErrorMessage());
            r.setData("error_code", oe.getErrorCode());
            r.setData("request_id", oe.getRequestId());
            r.setData("host_id", oe.getHostId());
        } catch (Exception ex) {
            BaseExceptionHandler.printStackTrace(ex);

            log.error(ex.getMessage());
            r = EnumSys.SYS.getR();
            r.setErrMsg(ex.getMessage());
        } finally {
            if (ossClient != null) {
                ossClient.shutdown();
            }
        }

        errHandle("fileList", r);
        if (r.err()) {
            XValidator.err(r);
        }

        return r;
    }

    /**
     * 删除x天前的目录及目录下的文件
     *
     * @param root       前缀，如：export
     * @param delDirList 删除x天前数据
     */
    public XReturn delDirAndFile(String root, List<String> delDirList) {
        List<OSSObjectSummary> files = fileList(root);
        List<String> fileKeys = new ArrayList<>();

        //LocalDateTime deleteDate = Helper.funcToday().minusDays(beforeDay);
        //Date date = Date.from(deleteDate.atZone(ZoneId.systemDefault()).toInstant());
        //log.debug("删除日期：{}", Helper.funcToDateTimeString(deleteDate));

        for (OSSObjectSummary file : files) {
            if (root.equals(file.getKey())) continue;

            String[] fileArr = file.getKey().split("/");
            if (fileArr.length < 1) {
                continue;
            }
            String dirName = fileArr[1];
            if (delDirList.contains(dirName)) {
                log.debug("file_key: {}, dirName: {}", file.getKey(), dirName);
                fileKeys.add(file.getKey());
            }
        }

        if (fileKeys.size() > 0) {
            delFileList(fileKeys);
        }

        return EnumSys.OK.getR();
    }

    public XReturn generatePresignedUrl(String ossfilePath) {
        return generatePresignedUrl(ossfilePath, 60);
    }

    /**
     * 生成访问链接，60秒有效
     *
     * @param ossfilePath  oss路径
     * @param expireSecond 有效时长（秒）
     */
    public XReturn generatePresignedUrl(String ossfilePath, int expireSecond) {
        OSS ossClient = getClient();
        XReturn r;
        try {
            // 设置签名URL过期时间，单位为毫秒。本示例以设置过期时间为1小时为例。
            Date expiration = new Date(new Date().getTime() + expireSecond * 1000L);
            // 生成以GET方法访问的签名URL，访客可以直接通过浏览器访问相关内容。
            URL url = ossClient.generatePresignedUrl(getBucketName(), ossfilePath, expiration);
            r = XReturn.getR(0);
            r.setData("url", url.toString());
        } catch (OSSException oe) {
            log.error(oe.getMessage());

            r = EnumSys.SYS.getR();
            r.setErrMsg(oe.getErrorMessage());
            r.setData("error_code", oe.getErrorCode());
            r.setData("request_id", oe.getRequestId());
            r.setData("host_id", oe.getHostId());
        } catch (Exception ex) {
            BaseExceptionHandler.printStackTrace(ex);

            log.error(ex.getMessage());
            r = EnumSys.SYS.getR();
            r.setErrMsg(ex.getMessage());
        } finally {
            if (ossClient != null) {
                ossClient.shutdown();
            }
        }

        return r;
    }


    protected void errHandle(String method, XReturn r) {
        if (r.err()) {
            LogPipeLine.of().saveDb(method, XHttpContext.getX());
        } else {
            LogPipeLine.of().clear();
        }
    }

    @SneakyThrows
    protected OSS getClient() {
        // yourEndpoint填写Bucket所在地域对应的Endpoint。以华东1（杭州）为例，Endpoint填写为https://oss-cn-hangzhou.aliyuncs.com。
        String endPoint = getConfig().getEndpoint();

        ClientBuilderConfiguration clientBuilderConfiguration = new ClientBuilderConfiguration();
        //clientBuilderConfiguration.setSignatureVersion(SignVersion.V4);

        //OSS ossClient = OSSClientBuilder.create()
        //        .region(getConfig().getApi_region())
        //        .endpoint(endPoint)
        //        .clientConfiguration(clientBuilderConfiguration)
        //        .build();

        // 创建OSSClient实例。
        OSS ossClient = new OSSClientBuilder().build(endPoint, getConfig().getAccess_key_id(), getConfig().getAccess_key_secret(), clientBuilderConfiguration);

        return ossClient;
    }

    public com.aliyun.sts20150401.Client getStsClient() throws Exception {
        // 建议使用更安全的 STS 方式。
        com.aliyun.teaopenapi.models.Config config = new com.aliyun.teaopenapi.models.Config()
                // 必填，请确保代码运行环境设置了环境变量 OSS_ACCESS_KEY_ID。
                .setAccessKeyId(getConfig().getAccess_key_id())
                // 必填，请确保代码运行环境设置了环境变量 OSS_ACCESS_KEY_SECRET。
                .setAccessKeySecret(getConfig().getAccess_key_secret());
        // Endpoint 请参考 https://api.aliyun.com/product/Sts
        config.endpoint = getConfig().getSts_endpoint();
        return new com.aliyun.sts20150401.Client(config);
    }

    /**
     * 获取STS临时凭证
     *
     * @return AssumeRoleResponseBodyCredentials 对象
     */
    public AssumeRoleResponseBody.AssumeRoleResponseBodyCredentials getStsCredential(String sessionName) throws Exception {
        AssumeRoleResponseBody.AssumeRoleResponseBodyCredentials stsCredentials = null;
        //读取缓存
        String key = "aliyun|sts|" + sessionName;
        String stsCache = XRedis.getBean().get(key);
        if (XStr.hasAnyText(stsCache)) {
            return XJson.parseObject(stsCache, AssumeRoleResponseBody.AssumeRoleResponseBodyCredentials.class);
        }

        String arn = getConfig().getSts_arn();

        com.aliyun.sts20150401.Client client = getStsClient();
        com.aliyun.sts20150401.models.AssumeRoleRequest assumeRoleRequest = new com.aliyun.sts20150401.models.AssumeRoleRequest()
                // 必填，请确保代码运行环境设置了环境变量 OSS_STS_ROLE_ARN
                .setRoleArn(arn)
                .setDurationSeconds(3600L)
                .setRoleSessionName(sessionName);// 自定义会话名称
        com.aliyun.teautil.models.RuntimeOptions runtime = new com.aliyun.teautil.models.RuntimeOptions();
        try {
            // 复制代码运行请自行打印 API 的返回值
            AssumeRoleResponse response = client.assumeRoleWithOptions(assumeRoleRequest, runtime);
            // credentials里包含了后续要用到的AccessKeyId、AccessKeySecret和SecurityToken。
            stsCredentials = response.body.credentials;

            XRedis.getBean().setEx(key, XJson.toJSONString(stsCredentials), 3000);
        } catch (TeaException error) {
            // 此处仅做打印展示，请谨慎对待异常处理，在工程项目中切勿直接忽略异常。
            // 错误 message
            log.error(error.getMessage());
            // 诊断地址
            log.error("{}", error.getData().get("Recommend"));
            com.aliyun.teautil.Common.assertAsString(error.message);
        } catch (Exception _error) {
            _error.printStackTrace();
            TeaException error = new TeaException(_error.getMessage(), _error);
            // 此处仅做打印展示，请谨慎对待异常处理，在工程项目中切勿直接忽略异常。
            // 诊断地址
            log.error("{}", error.getData().get("Recommend"));
            com.aliyun.teautil.Common.assertAsString(error.message);
        }

        // 返回一个默认的错误响应对象，避免返回null
        if (stsCredentials == null) {
            stsCredentials = new AssumeRoleResponseBody.AssumeRoleResponseBodyCredentials();
            stsCredentials.accessKeyId = "ERROR_ACCESS_KEY_ID";
            stsCredentials.accessKeySecret = "ERROR_ACCESS_KEY_SECRET";
            stsCredentials.securityToken = "ERROR_SECURITY_TOKEN";
        }
        return stsCredentials;
    }

    /**
     * 签名加密
     *
     * @param key
     * @param data
     */
    public static byte[] stsHmacsha256(byte[] key, String data) {
        try {
            // 初始化HMAC密钥规格，指定算法为HMAC-SHA256并使用提供的密钥。
            SecretKeySpec secretKeySpec = new SecretKeySpec(key, "HmacSHA256");

            // 获取Mac实例，并通过getInstance方法指定使用HMAC-SHA256算法。
            Mac mac = Mac.getInstance("HmacSHA256");
            // 使用密钥初始化Mac对象。
            mac.init(secretKeySpec);

            // 执行HMAC计算，通过doFinal方法接收需要计算的数据并返回计算结果的数组。
            byte[] hmacBytes = mac.doFinal(data.getBytes());

            return hmacBytes;
        } catch (Exception e) {
            throw new RuntimeException("Failed to calculate HMAC-SHA256", e);
        }
    }

    /**
     * 获取上传参数
     */
    @SneakyThrows
    public XReturn stsSignForOssUpload(String sessionName) {
        EnvAlipayOss config = getConfig();
        AssumeRoleResponseBody.AssumeRoleResponseBodyCredentials sts_data = getStsCredential(sessionName);

        String accesskeyid = sts_data.accessKeyId;
        String accesskeysecret = sts_data.accessKeySecret;
        String securitytoken = sts_data.securityToken;

        //获取x-oss-credential里的date，当前日期，格式为yyyyMMdd
        //String date = XDate.format(XDate.now(), "yyyyMMdd");
        ZonedDateTime today = ZonedDateTime.now().withZoneSameInstant(java.time.ZoneOffset.UTC);
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyyMMdd");
        String date = today.format(formatter);

        //获取x-oss-date
        ZonedDateTime now = ZonedDateTime.now().withZoneSameInstant(ZoneOffset.UTC);
        DateTimeFormatter formatter2 = DateTimeFormatter.ofPattern("yyyyMMdd'T'HHmmss'Z'");
        String x_oss_date = now.format(formatter2);

        // 步骤1：创建policy。
        String x_oss_credential = accesskeyid + "/" + date + "/" + getConfig().getRegion() + "/oss/aliyun_v4_request";
        log.info("x_oss_credential: {}", x_oss_credential);
        ObjectMapper mapper = new ObjectMapper();

        Map<String, Object> policy = new HashMap<>();
        //指定过期时间，单位为秒。
        Long expire_time = 3600L;
        policy.put("expiration", XApp.secondsToIso6801(expire_time));

        List<Object> conditions = new ArrayList<>();

        Map<String, String> bucketCondition = new HashMap<>();
        bucketCondition.put("bucket", config.getBucket_name());
        conditions.add(bucketCondition);

        Map<String, String> securityTokenCondition = new HashMap<>();
        securityTokenCondition.put("x-oss-security-token", securitytoken);
        conditions.add(securityTokenCondition);

        Map<String, String> signatureVersionCondition = new HashMap<>();
        signatureVersionCondition.put("x-oss-signature-version", "OSS4-HMAC-SHA256");
        conditions.add(signatureVersionCondition);

        Map<String, String> credentialCondition = new HashMap<>();
        credentialCondition.put("x-oss-credential", x_oss_credential); // 替换为实际的 access key id
        conditions.add(credentialCondition);

        Map<String, String> dateCondition = new HashMap<>();
        dateCondition.put("x-oss-date", x_oss_date);
        conditions.add(dateCondition);

        int uploadSizeMax = 1024000 * SysConfig.getIns().getUploadFileSizeMax(); //100M
        conditions.add(Arrays.asList("content-length-range", 1, uploadSizeMax));
        conditions.add(Arrays.asList("eq", "$success_action_status", "200"));
        conditions.add(Arrays.asList("starts-with", "$key", config.getDir_name()));

        policy.put("conditions", conditions);

        String jsonPolicy = mapper.writeValueAsString(policy);

        // 步骤2：构造待签名字符串（StringToSign）。
        String stringToSign = Base64.getEncoder().encodeToString(jsonPolicy.getBytes());
        // System.out.println("stringToSign: " + stringToSign);

        // 步骤3：计算SigningKey。
        byte[] dateKey = stsHmacsha256(("aliyun_v4" + accesskeysecret).getBytes(), date);
        byte[] dateRegionKey = stsHmacsha256(dateKey, getConfig().getRegion());
        byte[] dateRegionServiceKey = stsHmacsha256(dateRegionKey, "oss");
        byte[] signingKey = stsHmacsha256(dateRegionServiceKey, "aliyun_v4_request");

        // 步骤4：计算Signature。
        byte[] result = stsHmacsha256(signingKey, stringToSign);
        String signature = BinaryUtil.toHex(result);


        XReturn r = XReturn.getR(0);
        // 将数据添加到 map 中
        r.put("version", "OSS4-HMAC-SHA256");
        r.put("policy", stringToSign);
        r.put("x_oss_credential", x_oss_credential);
        r.put("x_oss_date", x_oss_date);
        r.put("signature", signature);
        r.put("security_token", securitytoken);
        r.put("dir", config.getDir_name() + "/");
        r.put("host", getConfig().getBucket_host());

        return r;
    }
}

